/* run2 -- Wrapper program for console mode programs under Windows(TM)
 * Copyright (c) 2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */
#ifdef _MSC_VER
# define _CRT_SECURE_NO_DEPRECATE 1
#endif

#if HAVE_CONFIG_H
# include <config.h>
#endif

#define WIN32

/* want windows XP or above */
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define NOMINMAX
#include <windows.h>

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>
#include <winuser.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#if HAVE_LOCALE_H
# include <locale.h>
#endif

#include <getopt.h>
#include <errno.h>
#include <assert.h>

#if defined(__CYGWIN__)
# include <sys/cygwin.h>
#else
# include <direct.h>
#endif

#include "lib/util.h"
#include "lib/confdata.h"
#include "lib/confparser.h"
#include "lib/target.h"
#include "lib/env.h"
#include "run2_gpl.h"

#if defined(PATH_MAX)
# define RUN2_PATHMAX PATH_MAX
#elif defined(MAXPATHLEN)
# define RUN2_PATHMAX MAXPATHLEN
#else
# define RUN2_PATHMAX 1024
#endif

typedef struct s_opts_ {
  char* xmlfile;
  char* displayname;
  char* xlibname;
} s_opts;

int parse_args (int argc, char *argv[], s_opts *sopt, int from_xml);
int realMain (int argc, char *argv[]);
int WINAPI WinMain (HINSTANCE hSelf, HINSTANCE hPrev, LPSTR cmdline, int nShow);
int core_impl (run2_confdata_t *data, s_opts *sopt);

void cleanup(s_opts* s)
{
  if (!s) return;
  if (s->xmlfile)     { free(s->xmlfile);     s->xmlfile = NULL; }
  if (s->displayname) { free(s->displayname); s->displayname = NULL; }
  if (s->xlibname)    { free(s->xlibname);    s->xlibname    = NULL; }
}

int opt_nogui = 0;
int opt_notty = 0;
double opt_timeout = 0.5;
#if defined(DEBUG)
int opt_loglevel = RUN2_LOG_DEBUG;
#elif defined(SILENT)
int opt_loglevel = RUN2_DEFAULT_LOG_SILENT_LEVEL;
#else
int opt_loglevel = RUN2_DEFAULT_LOG_LEVEL;
#endif
int opt_wait = 0;
int opt_force = 0;

#define FORCE_AUTO 0
#define FORCE_GDI  1
#define FORCE_X11  2
static const char *
opt_force_str(int c)
{
  switch (c)
    {
      case FORCE_AUTO: return "auto";
      case FORCE_GDI: return "GDI";
      case FORCE_X11: return "X11";
      default: return "<unknown>";
    }
}

void dumpOpts(s_opts* s)
{
  if (s) {
    if (s->xmlfile)     { debugMsg(1, "xmlfile     : '%s'", s->xmlfile); }
    if (s->displayname) { debugMsg(1, "displayname : '%s'", s->displayname); }
    if (s->xlibname)    { debugMsg(1, "xlibname    : '%s'", s->xlibname);    }
  }
  debugMsg(1, "opt_loglevel: %d", opt_loglevel);
  debugMsg(1, "opt_nogui   : %d", opt_nogui);
  debugMsg(1, "opt_notty   : %d", opt_notty);
  debugMsg(1, "opt_timeout : %5.2f", opt_timeout);
  debugMsg(1, "opt_wait    : %d", opt_wait);
  debugMsg(1, "opt_force   : %s", opt_force_str(opt_force));
}

int WINAPI
WinMain (HINSTANCE hSelf, HINSTANCE hPrev, LPSTR cmdline, int nShow)
{
  char **argv, *p, **q;
  int argc;
  char bufArg0[RUN2_PATHMAX];
  char bufTemp[RUN2_PATHMAX];
  char* fullCmdLine;
  int rc, i;

  opt_nogui = 0;
  opt_notty = 0;
#if defined(DEBUG)
  opt_loglevel = RUN2_LOG_DEBUG;
#elif defined(SILENT)
  opt_loglevel = RUN2_DEFAULT_LOG_SILENT_LEVEL;
#else
  opt_loglevel = RUN2_DEFAULT_LOG_LEVEL;
#endif

  setlocale(LC_ALL, "C");

  debugMsg(1, "orig cmdline = %s", cmdline);
  GetModuleFileName(NULL, bufTemp, sizeof(bufTemp));
  cygwin_conv_to_full_posix_path(bufTemp, bufArg0);

  fullCmdLine = (char*) run2_malloc(strlen(bufArg0) + strlen(cmdline) + 4);

  /* protect embedded spaces in bufArg0 */
  strcpy(fullCmdLine, "\"");
  strcat(fullCmdLine, bufArg0);
  strcat(fullCmdLine, "\" ");
  strcat(fullCmdLine, cmdline);

  debugMsg(1, "new cmdline = %s", fullCmdLine);

  if ((rc = run2_split_string (fullCmdLine, &argc, &argv)) != 0)
    {
      errorMsg ("Could not create argv vector from |%s|", fullCmdLine);
      goto cleanup;
    }

  debugMsg(1, "argc = %d", argc);
  if(argv) {
    q = argv;
    for (q = argv; *q != NULL; q++)
      debugMsg(1, ": '%s'", *q);
  }

  for (i = 0; i < argc; i++) {
    debugMsg(1, "argv[%d]: '%s'", i, argv[i]);
  }

  rc = realMain(argc, argv);

cleanup:
  run2_freeargv(argv);

  infoMsg("Exiting with status %d", rc);
  /* note that this will kill the detached thread if it's still running */
  return rc;
}

int
realMain(int argc, char* argv[])
{
  int i, c, count = 0;
  int save_default_log_level;
  s_opts sopt;
  char* p;
  run2_confdata_t *data = NULL;
  int xml_argc = 0;
  char **xml_argv = NULL;

  int rc = 0;
  sopt.xmlfile = NULL;
  sopt.displayname = NULL;
  sopt.xlibname = NULL;

  /* allow error messages during option parsing */
  run2_set_program_name(argv[0]);
  save_default_log_level = run2_get_verbose_level();
  run2_set_verbose_level(RUN2_DEFAULT_LOG_LEVEL);
  rc = parse_args(argc, argv, &sopt, 0);
  run2_set_verbose_level (save_default_log_level);
  if (rc != 0)
    {
      rc = (rc < 0 ? -rc : rc);
      goto cleanup_realMain;
    }

  /* propagate options to utils */
  run2_set_tty_mode(!opt_notty);
  run2_set_gui_mode(!opt_nogui);
  run2_set_verbose_level(opt_loglevel);

  dumpOpts(&sopt);

  /* now, parse the xmlfile */
  data = run2_xml_parse (sopt.xmlfile);
  if (!data)
    goto cleanup_realMain;

  if (run2_tty_is_allowed())
    if (RUN2_LOG_DEBUG <= run2_get_verbose_level())
      run2_confdata_print (stderr, 0, sopt.xmlfile, data);

  /* get the self options, and reparse */
  run2_selfoptions_t * selfopt = run2_confdata_get_selfoptions (data);
  run2_create_argv_from_dlist (selfopt->args, argv[0], &xml_argc, &xml_argv);
  optind = 0;
  rc = parse_args(xml_argc, xml_argv, &sopt, 1);
  if (rc != 0)
    {
      rc = (rc < 0 ? -rc : rc);
      goto cleanup_realMain;
    }

  /* propagate (updated?) options to utils */
  run2_set_tty_mode(!opt_notty);
  run2_set_gui_mode(!opt_nogui);
  run2_set_verbose_level(opt_loglevel);

  dumpOpts(&sopt);

  rc = core_impl (data, &sopt);

cleanup_realMain:
  cleanup (&sopt);
  if (data)
    {
      run2_confdata_delete (data);
      data = NULL;
    }
  if (xml_argv)
    {
      run2_freeargv (xml_argv);
      xml_argv = NULL;
    }
  debugMsg (1, "returning with status %d", rc);
  return rc;
}

static struct option long_options[] =
  {
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'v'},
    {"display", required_argument, NULL, 'd'},
    {"xlibname", required_argument, NULL, 'x'},
    {"timeout", required_argument, NULL, 't'},
    {"wait", no_argument, &opt_wait, 1},
    {"force-gdi", no_argument, &opt_force, FORCE_GDI},
    {"force-x11", no_argument, &opt_force, FORCE_X11},
#if (ENABLE_GUI == 1)
    {"nogui", no_argument, &opt_nogui, 1},
#endif
#if (ENABLE_TTY == 1)
    {"notty", no_argument, &opt_notty, 1},
#endif
    {"debug", optional_argument, NULL, 'D'},
    {"no-silent", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_LEVEL},
    {"silent", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_SILENT_LEVEL},
    {"no-verbose", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_LEVEL},
    {"verbose", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_VERBOSE_LEVEL},
    {0, 0, 0, 0}
  };

static char* helpString =
    "Usage: %s [OPTIONS] xmlfile\n"
    "Launches a target app, based information specified in the xmlfile\n"
    "argument. Optionally, determines if an Xserver is available or not,\n"
    "and launches one of two different target apps. Also, may be used\n"
    "to set environment variables before launching the target.\n"
    "Options:\n"
    "  -h|--help                  print this help message and exit\n"
    "  -v|--version               print version information and exit\n"
    "  -d|--display STRING        use STRING instead of $DISPLAY\n"
    "  -x|--xlibname STRING       use exactly STRING instead of fuzzy cygX11-n.dll search\n"
    "  -t|--timeout FLOAT         allow FLOAT seconds to connect with Xserver\n"
    "                             defaults to 0.5, use 0.0 for Xlib's (safe, 12s) timeout\n"
    "     --force-gdi             unconditionally launch GDI configuration\n"
    "     --force-x11             unconditionally launch X11 configuration\n"
    "  -w|--wait                  wait for the target app to complete before returning\n"
#if (ENABLE_GUI == 1)
    "     --nogui                 disable informational popups\n"
#endif
#if (ENABLE_TTY == 1)
    "     --notty                 disable stderr messages\n"
#endif
    "     --silent                suppress all but fatal error messages"
#ifdef SILENT
" (default)\n"
#else
"\n"
#endif
    "     --no-silent             allow (fatal and non-fatal) error messages"
#ifndef SILENT
" (default)\n"
#else
"\n"
#endif
    "     --no-verbose            same as --no-silent\n"
    "     --verbose               allow error, warning, and info messages\n"
    "     --debug[=N]             turn on debugging messages, as well as\n"
    "                             those enabled by --verbose. N defaults to 1\n"
    "                             --debug=0 is the same as --verbose. Values\n"
    "                             higher than 1 enable increasing details\n";

int parse_args(int argc, char* argv[], s_opts* sopt, int from_xml)
{
  int i, c, count = 0;
  char *p;
  while (1)
    {
      int option_index = 0;
      c = getopt_long(argc, argv, "hvd:x:t:", long_options, &option_index);
      if (c == -1)
        break;

      switch (c)
        {
          case 0:
            /* If this option set a flag, do nothing else now. */
            if (long_options[option_index].flag != 0)
              break;
            /* else fall thru */
          case 'h':
            realMsg (helpString, run2_get_program_name());
            return 1;
          case 'v':
            realMsg (PACKAGE_STRING);
            return 1;
          case 'd':
            if (sopt->displayname) { free(sopt->displayname); sopt->displayname = NULL; }
            sopt->displayname = run2_strdup(optarg);
            break;
          case 'x':
            if (sopt->xlibname) { free(sopt->xlibname); sopt->xlibname = NULL; }
            sopt->xlibname = run2_strdup(optarg);
            break;
          case 't':
            opt_timeout = strtod (optarg, &p);
            if (optarg == p) {
              errorMsg("bad value specified for timeout: %s\n", optarg);
              return -1;
            }
            break;
          case 'D':
            realMsg ("setting debug level to %s\n", optarg);
            if (optarg)
              {
                long val = 0;
                if (run2_strtol(optarg, &val) != 0)
                  {
                    errorMsg("bad value specified for debug: %s\n", optarg);
                    return -1;
                  }
                if (val <= 0) opt_loglevel = RUN2_DEFAULT_LOG_VERBOSE_LEVEL;
                else opt_loglevel = (val-1) + RUN2_LOG_DEBUG;
              }
            else
              opt_loglevel = RUN2_LOG_DEBUG;
            break;
          case '?':
            /* getopt_long already printed an error message. */
            return -1;
          default:
            errorMsg("unrecognized option");
            return -1;
        }
    }
  /* if everything is turned off, AND we have requested debugging,
     then send it to the tty */
  if ((opt_nogui || (!ENABLE_GUI)) && (opt_notty || (!ENABLE_TTY)))
    if (opt_loglevel >= RUN2_LOG_DEBUG)
      opt_notty = 0;

  {
    int min_args = (from_xml ? 0 : 1);
    int max_args = (from_xml ? 0 : 1);
    if (argc - optind > max_args)
      {
        errorMsg ("Too many arguments!");
        realMsg (helpString, run2_get_program_name());
        return -1;
      }
    if (argc - optind < min_args)
      {
        errorMsg ("Missing required arguments!");
        realMsg (helpString, run2_get_program_name());
        return -1;
      }
  }
  if (!from_xml)
    {
      if (sopt->xmlfile) { free(sopt->xmlfile); sopt->xmlfile= NULL; }
      sopt->xmlfile = run2_strdup(argv[optind]);
    }
  return 0;
}

int
core_impl (run2_confdata_t *data, s_opts *sopt)
{
  run2_global_t * global;
  run2_tgtopts_t * gdi;
  run2_tgtopts_t * x11;
  run2_tgt_spec_t * tgtspec;
  int global_has_tgt = 0;
  char *startin;
  char *cmd;
  const char *which;
  DWORD retval;

  global = data->global;
  if (global)
    {
      tgtspec = global->tgt;
      if (tgtspec)
        {
          global_has_tgt = 1;
        }
    }
  gdi = data->gdi;
  x11 = data->x11;

  if (global_has_tgt && (gdi || x11))
    {
      errorMsg ("Can't specify a global <Target> and <GDI>/<X11> ones");
      return 1;
    }
  if (!global_has_tgt && (!gdi || !x11))
    {
      errorMsg ("Must specify both <GDI> and <X11> targets, or a global one");
      return 1;
    }

  if (global_has_tgt)
    {
      run2_env (data, 0);
      startin = run2_get_startin_directory (tgtspec);
      cmd = run2_create_cmdline_from_tgtspec (tgtspec);
      which="Global";
    }
  else
    {
      int choice = FORCE_GDI;
      if (!opt_force)
        {
          int noX11 = run2_checkX(NULL,
                                  NULL,
                                  NULL,
                                  NULL,
                                  sopt->displayname,
                                  opt_timeout,
                                  NULL);
          choice = (noX11 ? FORCE_GDI : FORCE_X11);
        }
      switch (choice)
        {
          case FORCE_GDI:
            run2_env (data, 1);
            tgtspec = gdi->tgt;
            startin = run2_get_startin_directory (tgtspec);
            cmd = run2_create_cmdline_from_tgtspec (tgtspec);
            which="GDI";
            break;
          case FORCE_X11:
            run2_env (data, 2);
            tgtspec = x11->tgt;
            startin = run2_get_startin_directory (tgtspec);
            cmd = run2_create_cmdline_from_tgtspec (tgtspec);
            which="X11";
            break;
          default:
            errorMsg ("Internal error: bad state");
            return 1;
        }
    }

  if (!cmd || !startin)
    {
      errorMsg ("Unable to construct startup parameters");
      if (cmd) free(cmd);
      if (startin) free(startin);
      return 1;
    }

  debugMsg (1, "[%s] cmd=|%s| startin=|%s|", which, cmd, startin);
  retval = run2_start_child (cmd, startin, opt_wait);
  if (retval)
    {
      errorMsg ("Cmd failed: |%s|", cmd);
     }
  free (cmd);
  free (startin);
  return (int) retval;
}

